using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using Pfz.Caching;
using InfFront.UICore;

namespace InfFront.InfoViews.Base
{
    public interface IIconedObject
    {
        System.IO.Stream GetIconStreamPng(int hSize, int vSize, bool disabled, bool highlighted);
    }

    /// <summary>
    /// View-side IInfoSource wrapper
    /// </summary>
    public class InfoViewSource : BaseInfoViewEntity, IInfoSourceCallbacks, IDisposable, IIconedObject
    {
        #region Private Variables
        private IInfoSource source;
        private InfoViewSource parent;
        private List<InfoViewField> fields = null;
        private List<InfoViewCommand> commands = null;
        private List<InfoViewFieldGroup> editGroups = null;
        private EventHandlerList events;
        private List<InfoViewSource> sources = null;
        private bool? needsEditBuffer = null;
        private bool? supportsSorting = null;
        private PropertyDescriptorCollection itemProperties;
        private WeakDictionary<IInfoEntry, InfoViewRecord> recordCache;
        private string iconFieldName = null;
        private InfoViewField iconField;
        private string titleFieldName = null;
        private InfoViewField titleField;
        #endregion

        #region Construction/Initialization
        protected InfoViewSource(IInfoSource source, IVisualSettingsAccessor visualSettings)
            : base(source.GetName(), visualSettings)
        {
            this.source = source;
            Init();
        }

        protected internal InfoViewSource(InfoViewSource parent, IInfoSource source)
            : base(source.GetName(), parent)
        {
            this.parent = parent;
            this.source = source;
            Init();
        }

        private void Init()
        {
            recordCache = new WeakDictionary<IInfoEntry, InfoViewRecord>();
            events = new EventHandlerList();
            InfoSourceSetCallbacksArgs scargs = new InfoSourceSetCallbacksArgs();
            scargs.Action = SourceSetCallbacksActionKind.Register;
            scargs.Callbacks = this;
            source.SetCallbacks(scargs);
        }
        #endregion

        internal InfoViewSource Parent
        {
            get { return parent; }
        }

        protected internal virtual RootInfoViewSource RootParent
        {
            get { return Parent.RootParent; }
        }

        public string[] Path
        {
            get { return InternalPath.ToArray(); }
        }

        protected virtual List<string> InternalPath
        {
            get
            {
                var path = Parent.InternalPath;
                path.Add(Name);
                return path;
            }
        }

        public string Title
        {
            get { return source.GetTitle(); }
        }

        public bool AllowsInplaceEdit
        {
            get { return source.GetAllowsInplaceEdit();  }
        }

        public bool IsDummy
        {
            get 
            {
                if (source == null)
                    throw new NullReferenceException("The connection is to be closed");
                return source.GetIsDummy(); 
            }
        }

        public int MaxEntryCount
        {
            get { return source.GetMaxEntryCount(); }
        }

        public bool NeedsEditBuffer
        {
            get 
            { 
                if (!needsEditBuffer.HasValue)
                    needsEditBuffer = source.GetNeedsEditBuffer();
                return needsEditBuffer.Value;
            }
        }

        public InfoViewField IconField
        {
            get
            {
                if (iconFieldName == null)
                {
                    iconFieldName = source.GetIconFieldName();
                    if (iconFieldName == null)
                        iconFieldName = string.Empty;
                    if (iconFieldName == string.Empty)
                        iconField = null;
                    else
                        iconField = FieldByName(iconFieldName);
                }
                return iconField;
            }
        }

        public InfoViewField TitleField
        {
            get
            {
                if (titleFieldName == null)
                {
                    titleFieldName = source.GetTitleFieldName();
                    if (titleFieldName == null)
                        titleFieldName = string.Empty;
                    if (titleFieldName == string.Empty)
                        titleField = null;
                    else
                        titleField = FieldByName(titleFieldName);
                }
                return titleField;
            }
        }

        public bool SupportsSorting
        {
            get
            {
                if (!supportsSorting.HasValue)
                    supportsSorting = source.GetSupportsSorting();
                return supportsSorting.Value;
            }
        }

        internal InfoViewRecord FindEntryWrapper(IInfoEntry entry, bool createNew)
        {
            lock (recordCache)
            {
                InfoViewRecord rv = recordCache[entry];
                if (rv != null || !createNew)
                    return rv;
                rv = new InfoViewRecord(entry, this);
                recordCache[entry] = rv;
                return rv;
            }
        }

        internal InfoViewRecord WrapEntry(IInfoEntry entry)
        {
            return FindEntryWrapper(entry, true);
        }

        private void EnsureFieldsAndGroups()
        {
            fields = new List<InfoViewField>();
            var groupsByNames = new Dictionary<string, InfoViewFieldGroup>();
            int maxFieldOrder = -1;
            bool wasEqualFieldOrders = false;
            foreach (IInfoField fld in source.GetFields())
            {
                var field = new InfoViewField(this, fld);
                var igroup = fld.GetEditGroup();
                if (igroup != null)
                {
                    InfoViewFieldGroup group = null;
                    string name = igroup.GetName();
                    if (!groupsByNames.TryGetValue(name, out group))
                    {
                        group = new InfoViewFieldGroup(this, igroup);
                        groupsByNames[name] = group;
                    }
                    field.SetEditGroup(group);
                    group.AddField(field);
                }
                // add a field in the sorted list
                int order = field.Order;
                if (order > maxFieldOrder)
                    maxFieldOrder = order;
                int i = 0;
                for (; i < fields.Count; ++i)
                {
                    var order2 = fields[i].Order;
                    if (order2 == order)
                        wasEqualFieldOrders = true;
                    else if (fields[i].Order > order)
                        break;
                }
                fields.Insert(i, field);
            }
            if (wasEqualFieldOrders || maxFieldOrder >= fields.Count) // when VisualSettings's stored values lost their actuality
            {
                for (int i = 0; i < fields.Count; ++i)
                    fields[i].Order = i;
            }

            editGroups = new List<InfoViewFieldGroup>();
            editGroups.AddRange(groupsByNames.Values);
            Comparison<InfoViewFieldGroup> cmp = (x, y) => { return x.Order - y.Order; };
            editGroups.Sort(cmp);
        }

        public IEnumerable<InfoViewSource> NestedSources
        {
            get
            {
                if (sources == null)
                {
                    sources = new List<InfoViewSource>();
                    foreach (IInfoSource src in source.GetChildSources())
                        sources.Add(new InfoViewSource(this, src));
                }
                return sources;
            }
        }
        public IEnumerable<InfoViewField> Fields
        {
            get
            {
                if (fields == null)
                    EnsureFieldsAndGroups();
                return fields;
            }
        }

        public IEnumerable<InfoViewFieldGroup> EditFieldGroups
        {
            get
            {
                if (editGroups == null)
                    EnsureFieldsAndGroups();
                return editGroups;
            }
        }

        public InfoViewField FieldByName(string name)
        {
            foreach (var field in Fields)
            {
                if (field.Name == name)
                    return field;
            }
            return null;
        }

        public IEnumerable<InfoViewCommand> Commands
        {
            get
            {
                if (commands == null)
                {
                    commands = new List<InfoViewCommand>();
                    foreach (var cmd in source.GetCommands())
                        commands.Add(new InfoViewCommand(this, cmd));
                    Comparison<InfoViewCommand> cmp = (x, y) => { return x.Order - y.Order; };
                    commands.Sort(cmp);
                }
                return commands;
            }
        }

        public InfoViewCommand CommandByName(string name)
        {
            foreach (var cmd in Commands)
            {
                if (cmd.Name == name)
                    return cmd;
            }
            return null;
        }

        public IEnumerable<bool> CheckCommandsEnabled(InfoViewEntry currentEntry)
        {
            var cmds = Commands;
            if (commands.Count == 0)
                yield break;
            var cmdNames = new string[commands.Count];
            int i = 0;
            foreach (var cmd in cmds)
                cmdNames[i++] = cmd.Name;
            var result = source.CheckCommandsEnabled((currentEntry == null)? null: currentEntry.Origin, cmdNames);
            foreach (bool enabled in result)
                yield return enabled;
        }

        internal IInfoEntries GetNewEntries(InfoSourceGetEntriesArgs args)
        {
            return source.GetEntries(args.MaxCount, args.GetFilterFieldNames(), args.GetFilterValues(),
                                     args.GetSortFieldNames(), args.GetSortFieldDirections());
        }

        private InfoViewEntries GetInternalEntries()
        {
            return new InfoViewEntries(this);
        }

        /// <summary>
        /// Opens the InfoSource data stream reader.
        /// </summary>
        /// <returns>Readonly proxy data list. Optimized for sequential (no calls to this[.] and/or Count) access.</returns>
        public System.Collections.IList GetBindingSource()
        {
            return GetInternalEntries();
        }

        internal IEnumerable<InfoViewRecord> FindEntries(InfoViewField field, object value, int maxCount)
        {
            Debug.Assert(field.Owner == this);
            var args = new InfoSourceGetEntriesArgs();
            args.MaxCount = maxCount;
            args.Filters.Add(new InfoSourceGetEntriesArgs.FieldFilter() { FieldName = field.Name, Value = value });
            foreach (var entry in GetNewEntries(args))
                yield return WrapEntry(entry);
        }

        public InfoViewEntry GetFirstEntry()
        {
            foreach (InfoViewRecord rec in GetInternalEntries())
            {
                return new InfoViewEntry(this, rec);
            }
            return null;
        }

        private static IEnumerable<IDataType> GetIDataTypeEnumerable(IEnumerable<InfoViewField> fields)
        {
            foreach (var field in fields)
                yield return field.Type;
        }

        internal virtual object GetFieldValue(InfoViewField field, InfoViewRecord entry)
        {
            Debug.Assert(!entry.IsDisposed && entry.Owner == this);

            // send the request
            var stream = source.GetData(entry.Origin, new string[] { field.Name });

            // parse the result
            var unpacker = new FieldDataUnpacker(field.Type);
            var result = unpacker.UnpackFirst(stream);
            return result;
        }

        internal virtual void SetFieldValues(InfoViewRecord entry, Dictionary<InfoViewField, Object> fieldUpdates)
        {
            Debug.Assert(!entry.IsDisposed && entry.Owner == this);

            if (fieldUpdates.Count == 0)
                return;

            var fieldNames = new string[fieldUpdates.Count];
            int fldIdx = 0;
            foreach (var fld in fieldUpdates.Keys)
            {
                fieldNames[fldIdx] = fld.Name;
                ++fldIdx;
            }
            var packer = new FieldDataPacker(GetIDataTypeEnumerable(fieldUpdates.Keys));
            var data = new System.IO.MemoryStream();
            packer.Pack(data, fieldUpdates.Values.GetEnumerator());
            data.Seek(0, System.IO.SeekOrigin.Begin);
            source.SetData(entry.Origin, fieldNames, data);
        }

        internal virtual void SetFieldValue(InfoViewRecord entry, InfoViewField field, object value)
        {
            Debug.Assert(!entry.IsDisposed && entry.Owner == this);

            var packer = new FieldDataPacker(field.Type);
            var data = new System.IO.MemoryStream();
            packer.PackFirst(data, value);
            data.Seek(0, System.IO.SeekOrigin.Begin);
            source.SetData(entry.Origin, new string[] { field.Name }, data);
        }

        public InfoViewEntry ExtractEntry(object bindingListEntry)
        {
            if (bindingListEntry == null)
                return null;

            var asEntriesRecord = (InfoViewRecord)bindingListEntry;
            return new InfoViewEntry(this, asEntriesRecord);
        }

        private IImage GetIImage(int hSize, int vSize, bool disabled, bool highlighted, 
                                 IInfoEntry entry, string fieldName)
        {
            IconStyle style = IconStyle.Normal;
            if (disabled)
                style = highlighted? IconStyle.DisabledHighlighted: IconStyle.Disabled;
            else if (highlighted)
                style = IconStyle.Highlighted;
            var result = source.GetImage(hSize, vSize, style, entry, fieldName);
            return result;
        }

        internal System.IO.Stream GetImageStream(int hSize, int vSize, bool disabled, bool highlighted,
                                                 InfoViewRecord rec, InfoViewField field)
        {
            var result = RootParent.ExtractIImage(GetIImage(hSize, vSize, disabled, highlighted, 
                                                            (rec == null)? null: rec.Origin,
                                                            (field == null)? null: field.Name));
            return result;
        }

        System.IO.Stream IIconedObject.GetIconStreamPng(int hSize, int vSize, bool disabled, bool highlighted)
        {
            return GetImageStream(hSize, vSize, disabled, highlighted, null, null);
        }

        protected internal virtual PropertyDescriptorCollection GetRecordProperties()
        {
            if (itemProperties == null)
            {
                var result = new List<InfoFieldPropertyDescriptor>();
                foreach (InfoViewField fld in Fields)
                {
                    if (fld.IsGridVisible)
                        result.Add(new InfoFieldPropertyDescriptor(fld));
                }
                itemProperties = new PropertyDescriptorCollection(result.ToArray());
            }
            return itemProperties;
        }

        protected EventHandlerList Events
        {
            get { return events; }
        }

        private IExecutionControl PrepareExecControl(IInfoCommand command, InfoViewCommand.ExecutionArgs args)
        {
            var sendArgs = new ExecuteCommandArgs();
            sendArgs.Command = command;
            if (args.CurrentEntry != null)
                sendArgs.CurrentEntry = args.CurrentEntry.Origin;
            return source.ExecuteCommand(sendArgs);
        }

        internal void ExecuteCommand(IInfoCommand command, InfoViewCommand.ExecutionArgs args, bool post)
        {
            var exec = PrepareExecControl(command, args);
            RootParent.ExecAsyncCall(exec, !post);
        }

        internal void ExecuteCommandInSameThread(IInfoCommand command, InfoViewCommand.ExecutionArgs args)
        {
            var exec = PrepareExecControl(command, args);
            exec.Execute();
        }

        public void ExecuteListDoubleClick(InfoViewCommand.ExecutionArgs args)
        {
            var cmdName = source.GetListDoubleClickCommandName();
            if (string.IsNullOrEmpty(cmdName))
                return;
            var cmd = CommandByName(cmdName);
            if (cmd == null)
                throw new ArgumentException(string.Format("Unknown command name: '{0}'", cmdName));
            cmd.Execute(args);
        }

        private InfoViewSource RecursiveFindInfoSource(string name, InfoViewSource except, bool findInParent)
        {
            if (Name == name)
                return this;
            foreach (var child in NestedSources)
            {
                if (child == except)
                    continue;
                var result = child.RecursiveFindInfoSource(name, null, false);
                if (result != null)
                    return result;
            }
            if (findInParent)
                return Parent.RecursiveFindInfoSource(name, this, true);
            return null;
        }

        internal InfoViewSource FindInfoSourceByName(string name)
        {
            if (string.IsNullOrEmpty(name))
                return null;
            return RecursiveFindInfoSource(name, null, true);
        }

        #region Public Events

        private static object EVENT_REBUILT = new object();

        public event EventHandler OnRebuilt
        {
            add { lock (Events) Events.AddHandler(EVENT_REBUILT, value); }
            remove { lock (Events) Events.RemoveHandler(EVENT_REBUILT, value); }
        }

        private static object EVENT_LIST_REFRESHED = new object();

        public event EventHandler OnListRefreshed
        {
            add { lock (Events) Events.AddHandler(EVENT_LIST_REFRESHED, value); }
            remove { lock (Events) Events.RemoveHandler(EVENT_LIST_REFRESHED, value); }
        }

        private static object EVENT_CREATEENTRY = new object();

        public event EntryEditEventHandler OnCreateEntry
        {
            add { lock (Events) Events.AddHandler(EVENT_CREATEENTRY, value); }
            remove { lock (Events) Events.RemoveHandler(EVENT_CREATEENTRY, value); }
        }

        private static object EVENT_EDITENTRY = new object();

        public event EntryEditEventHandler OnEditEntry
        {
            add { lock (Events) Events.AddHandler(EVENT_EDITENTRY, value); }
            remove { lock (Events) Events.RemoveHandler(EVENT_EDITENTRY, value); }
        }

        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            InfoSourceSetCallbacksArgs scargs = new InfoSourceSetCallbacksArgs();
            scargs.Action = SourceSetCallbacksActionKind.Unregister;
            scargs.Callbacks = this;
            source.SetCallbacks(scargs);
            if (sources != null)
            {
                foreach (InfoViewSource src in sources)
                    src.Dispose();
                sources.Clear();
            }
            source = null;
            if (fields != null)
            {
                fields.Clear();
                fields = null;
            }
            if (this.events != null)
            {
                this.events.Dispose();
                this.events = null;
            }
        }
        #endregion

        #region IInfoSourceCallbacks Members

        void IInfoSourceCallbacks.OnSourceEntryRefreshed(IInfoEntry entry)
        {
            if (entry == null)
            {
                EventHandler handler = null;
                lock (Events)
                    handler = (EventHandler)Events[EVENT_LIST_REFRESHED];
                if (handler != null)
                    RootParent.ExecInMainThread(delegate() { handler(this, EventArgs.Empty); }, true);
            }
            else
            {
                var record = FindEntryWrapper(entry, false);
                if (record == null)
                    return;
                RootParent.ExecInMainThread(delegate() { record.FirePropertiesChanged(); }, true);
            }
        }

        void IInfoSourceCallbacks.OnSourceRebuilt()
        {
            EventHandler handler = null;
            lock (Events)
                handler = (EventHandler)Events[EVENT_REBUILT];
            if (handler != null)
                RootParent.ExecInMainThread(delegate() { handler(this, EventArgs.Empty); }, true);
        }

        void IInfoSourceCallbacks.BeginEntryEdit(IInfoEntry entry)
        {
            EntryEditEventHandler handler = null;
            lock (Events)
                handler = (EntryEditEventHandler)Events[EVENT_EDITENTRY];
            if (handler != null)
            {
                var args = new EntryEditEventArgs();
                var record = WrapEntry(entry);
                args.Entry = new InfoViewEntry(this, record);
                RootParent.ExecInMainThread(delegate() { handler(this, args); }, true);
            }
        }

        void IInfoSourceCallbacks.BeginEntryCreate(IInfoEntry entry)
        {
            EntryEditEventHandler handler = null;
            lock (Events)
                handler = (EntryEditEventHandler)Events[EVENT_CREATEENTRY];
            if (handler != null)
            {
                var args = new EntryEditEventArgs();
                var record = WrapEntry(entry);
                args.Entry = new InfoViewEntry(this, record);
                RootParent.ExecInMainThread(delegate() { handler(this, args); }, true);
            }
        }

        #endregion
    }

    public struct EntryEditEventArgs
    {
        public InfoViewEntry Entry { get; set; } 
    }

    public delegate void EntryEditEventHandler(object sender, EntryEditEventArgs args);


    /// <summary>
    /// IInfoSource.GetEntries function parameters
    /// </summary>
    internal class InfoSourceGetEntriesArgs
    {
        private List<SortDescription> sorts;
        private List<FieldFilter> filters;

        public InfoSourceGetEntriesArgs()
        {
            MaxCount = -1;
        }

        public int MaxCount { get; set; }

        public class SortDescription
        {
            /// <summary>
            /// The field name to sort by
            /// </summary>
            public string FieldName { get; set; }

            /// <summary>
            /// The sort direction
            /// </summary>
            public ListSortDirection Direction { get; set; }
        }

        /// <summary>
        /// The entry list sort descriptions
        /// </summary>
        public IList<SortDescription> Sortings
        {
            get 
            { 
                if (sorts == null)
                    sorts = new List<SortDescription>();
                return sorts; 
            }
        }

        public bool HasSortings
        {
            get { return (sorts != null) && sorts.Count > 0; }
        }

        public class FieldFilter
        {
            /// <summary>
            /// The field name to filter by
            /// </summary>
            public string FieldName { get; set; }

            /// <summary>
            /// The value to filter by
            /// </summary>
            public object Value { get; set; }
        }

        /// <summary>
        /// The entry list filter descriptions
        /// </summary>
        public IList<FieldFilter> Filters
        {
            get 
            {
                if (filters == null)
                    filters = new List<FieldFilter>();
                return filters; 
            }
        }

        #region IInfoSourceGetEntriesArgs Members

        public string[] GetSortFieldNames()
        {
            if (Sortings.Count == 0)
                return null;
            var names = new string[Sortings.Count];
            for (int i = 0; i < Sortings.Count; ++i)
                names[i] = Sortings[i].FieldName;
            return names;
        }

        public ListSortDirection[] GetSortFieldDirections()
        {
            if (Sortings.Count == 0)
                return null;
            var dirs = new ListSortDirection[Sortings.Count];
            for (int i = 0; i < Sortings.Count; ++i)
                dirs[i] = Sortings[i].Direction;
            return dirs;
        }

        public string[] GetFilterFieldNames()
        {
            if (Filters.Count == 0)
                return null;
            var names = new string[Filters.Count];
            for (int i = 0; i < Filters.Count; ++i)
                names[i] = Filters[i].FieldName;
            return names;
        }

        public object[] GetFilterValues()
        {
            if (Filters.Count == 0)
                return null;
            var values = new object[Filters.Count];
            for (int i = 0; i < Filters.Count; ++i)
                values[i] = Filters[i].Value;
            return values;
        }

        #endregion
    }
}
